package controllers

import command.CommandExec
import dao._
import implicits.BlastTool
import models.Tables._
import play.api.cache.AsyncCacheApi
import play.api.libs.json.Json
import play.api.mvc._
import tool.{FormTool, Tool}
import tool.Pojo.{CommandData, KeywordInfo}
import utils.Utils

import java.io.File
import javax.inject.Inject
import scala.concurrent.ExecutionContext.Implicits.global
import implicits.Implicits._
import org.apache.commons.io.FileUtils

import java.nio.file.Files


/**
 * Created by yz on 16/9/2021
 */
class ToolsController @Inject()(cc: ControllerComponents, cache: AsyncCacheApi)(
  implicit val basicDao: BasicDao,
  implicit val modeDao: ModeDao
) extends AbstractController(cc) with BlastTool {

  def blastBefore() = Action { implicit request =>
    Ok(views.html.tools.blast())
  }

  def blast = Action(parse.multipartFormData) { implicit request =>
    val data = FormTool.blastForm.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val seqFile = Tool.getSeqFile(tmpDir)
    data.queryText.map { queryText =>
      queryText.toFile(seqFile)
    }.getOrElse {
      val file = request.body.file("file").get
      file.ref.moveTo(seqFile, replace = true)
    }
    val outXmlFile = new File(tmpDir, "data.xml")
    val outHtmlFile = Tool.getOutHtmlFile(tmpDir)
    val outTableFile = Tool.getOutTableFile(tmpDir)
    val dbFile = Tool.getDbFile(data.againstType)
    val method = data.againstType match {
      case x if x.endsWith("Pep") => "blastp"
      case _ => "blastn"
    }
    val command =
      s"""
         | ${Tool.blastBinFile.unixPath}/${method} -query ${seqFile.unixPath}  -db ${dbFile.unixPath}  -outfmt 5 -evalue  ${data.evalue}  -max_target_seqs  ${data.maxTargetSeqs} -word_size ${data.wordSize}  -out  ${outXmlFile.unixPath}
         | python   ${Tool.blast2HtmlBinDir.unixPath}/blast2html.py  -i  ${outXmlFile.unixPath} -o  ${outHtmlFile.unixPath} --template ${Tool.jinjaDir.unixPath}/${method}.jinja
         |perl ${Tool.binPath.unixPath}/Blast2table -format 10 -xml ${outXmlFile.unixPath} -header >${outTableFile.unixPath}
         |""".stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }

    if (execCommand.isSuccess) {
      Ok(Json.obj("valid" -> true, "path" -> tmpDir.getAbsolutePath.replaceAll("\\\\", "/"),
        "tableData" -> outTableFile.str))
    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def blastResultBefore = Action {
    implicit request =>
      val data = FormTool.pathForm.bindFromRequest().get
      val kind = FormTool.kindForm.bindFromRequest().get.kind
      Ok(views.html.tools.blastResult(data.path, kind))
  }

  def blastResult = Action { implicit request =>
    val data = FormTool.pathForm.bindFromRequest().get
    val tmpDir = new File(data.path)
    val htmlFile = Tool.getOutHtmlFile(tmpDir)
    val block = tmpDir.getSyntenyData
    val json = Json.obj("html" -> s"${htmlFile.str}\n${Tool.scriptHtml}", "block" -> block)
    Tool.deleteDirectory(tmpDir)
    Ok(json)
  }

  def downloadByName = Action { implicit request =>
    val data = FormTool.nameForm.bindFromRequest().get
    val kind = FormTool.kindForm.bindFromRequest().get.kind
    val faFile = Tool.getDbFile(kind)
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val seqFile = Tool.getSeqFile(tmpDir)
    val command =
      s"""
         |samtools faidx  ${faFile.unixPath} "${data.name}"  >${seqFile.unixPath}
         |""".stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      Ok.sendFile(seqFile, onClose = () => {
        Tool.deleteDirectory(tmpDir)
      }).withHeaders(
        CONTENT_DISPOSITION -> s"attachment; filename=${data.name}_complete.fasta",
        CONTENT_TYPE -> "application/x-download"
      )
    } else {
      NotFound
    }
  }

  def downloadByRange = Action { implicit request =>
    val data = FormTool.nameForm.bindFromRequest().get
    val kind = FormTool.kindForm.bindFromRequest().get.kind
    val range = FormTool.rangeForm.bindFromRequest().get.range
    val faFile = Tool.getDbFile(kind)
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val seqFile = Tool.getSeqFile(tmpDir)
    val rangeStr = range.replaceAll("^.*:", "").trim.mySplit("to").map(_.trim).
      sortBy(_.toInt).mkString("-")
    val rangeInfo = s"${data.name}:${rangeStr}"
    val command =
      s"""
         |samtools faidx  ${faFile.unixPath} "${rangeInfo}"  >${seqFile.unixPath}
         |""".stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      Ok.sendFile(seqFile, onClose = () => {
        Tool.deleteDirectory(tmpDir)
      }).withHeaders(
        CACHE_CONTROL -> "max-age=3600",
        CONTENT_DISPOSITION -> s"attachment; filename=${data.name}_aligned.fasta",
        CONTENT_TYPE -> "application/x-download"
      )
    } else {
      NotFound
    }
  }

  def seqFetchBefore = Action { implicit request =>
    Ok(views.html.tools.seqFetch())
  }

  def seqQuery = Action { implicit request =>
    val data = FormTool.regionForm.bindFromRequest.get
    val species = FormTool.speciesForm.bindFromRequest().get.species
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val outFile = new File(tmpDir, "data.txt")
    val queryStr = data.chr + ":" + data.start + "-" + data.end
    val faFile = Tool.getGenomeFile(species)
    val execCommand = CommandExec().exec { b =>
      val command =
        s"""
           |samtools faidx  ${faFile.unixPath} ${queryStr}  >${outFile.unixPath}
           |""".stripMargin
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      val dataStr = FileUtils.readFileToString(outFile)
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("data" -> dataStr, "valid" -> true))
    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def seqMultipleQuery = Action { implicit request =>
    val data = FormTool.regionStrForm.bindFromRequest.get
    val species = FormTool.speciesForm.bindFromRequest().get.species
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val outFile = new File(tmpDir, "data.txt")
    val queryStr = data.region
    val faFile = Tool.getGenomeFile(species)
    val execCommand = CommandExec().exec { b =>
      val command =
        s"""
           |samtools faidx  ${faFile.unixPath} ${queryStr}  >${outFile.unixPath}
           |""".stripMargin
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      val dataStr = FileUtils.readFileToString(outFile)
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("data" -> dataStr, "valid" -> true))
    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def enrichBefore = Action { implicit request =>
    Ok(views.html.tools.enrich())
  }

  def keggEnrich = Action(parse.multipartFormData) { implicit request =>
    val data = FormTool.keggEnrichForm.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val diffFile = new File(tmpDir, "diff.txt")
    data.geneId.map { geneId =>
      val geneIds = geneId.mySplit(",")
      ("Geneid" :: geneIds).toFile(diffFile)
    }.getOrElse {
      val file = request.body.file("file").get
      file.ref.moveTo(diffFile, replace = true)
      Tool.dealInputFile(diffFile)
    }
    val outFile = new File(tmpDir, "ko.Ko.enrich.xls")
    val enrichFile = new File(Tool.enrichBinDir, "enrich/kegg_enrich.pl")
    val isHuman = if (List("Human_HG19", "Human_HG38").contains(data.database)) {
      "yes"
    } else "no"
    val command =
      s"""
         |dos2unix *
         |perl ${enrichFile.unixPath} ${diffFile.unixPath} ${Tool.enrichBinDir.unixPath}/temp/${data.database}/pathway.txt ko ${isHuman}
         |${"Rscript".condaPath()}  ${Tool.enrichBinDir.unixPath}/Ko_dodge_plot.R -i ko.Ko.bar.dat -o ./ -in ko_dodge -if pdf -sm 50 -n 15
         |convert  -density 300 ko_dodge.pdf ko_dodge.png
         |convert  -density 300 ko.Ko.enrich.pdf ko.Ko.enrich.png
       """.stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      val (map, array) = Utils.getInfoByLines(outFile.txtLines)
      val barBase64 = new File(tmpDir, "ko_dodge.png").base64
      val enrichBase64 = new File(tmpDir, "ko.Ko.enrich.png").base64
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("array" -> array, "map" -> map, "valid" -> true, "bar" -> barBase64, "enrich" -> enrichBase64))
    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def goEnrich = Action(parse.multipartFormData) { implicit request =>
    val data = FormTool.keggEnrichForm.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val diffFile = new File(tmpDir, "diff.txt")
    data.geneId.map { geneId =>
      ("Geneid" :: geneId.mySplit(",")).toFile(diffFile)
    }.getOrElse {
      val file = request.body.file("file").get
      file.ref.moveTo(diffFile, replace = true)
      Tool.dealInputFile(diffFile)
    }
    val command =
      s"""
         |perl ${Tool.enrichBinDir.unixPath}/go_enrich.pl ${diffFile.unixPath} ${Tool.enrichBinDir.unixPath}/temp/${data.database}/all.gene.list  ${Tool.enrichBinDir.unixPath}/temp/${data.database}/GO.list go
         |${"Rscript".condaPath()}  ${Tool.enrichBinDir.unixPath}/Go_stack_plot.R -i go.Go.bar.dat -o ./ -in go_stack -if pdf -sm 50 -n 15
         |convert  -density 300 go_stack.pdf go_stack.png
         |convert  -density 300 go.Go.enrich.pdf go.Go.enrich.png
         |""".stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      val outFile = new File(tmpDir, "go.Go.enrich.xls")
      val (map, array) = Utils.getInfoByLines(outFile.txtLines)
      val barBase64 = new File(tmpDir, "go_stack.png").base64
      val enrichBase64 = new File(tmpDir, "go.Go.enrich.png").base64
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("array" -> array, "map" -> map, "valid" -> true, "bar" -> barBase64, "enrich" -> enrichBase64))
    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def muscleBefore = Action { implicit request =>
    Ok(views.html.tools.muscle())
  }

  def muscle = Action(parse.multipartFormData) { implicit request =>
    val data = FormTool.muscleForm.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val seqFile = new File(tmpDir, "seq.txt")
    data.queryText.map { queryText =>
      FileUtils.writeStringToFile(seqFile, queryText)
    }.getOrElse {
      val file = request.body.file("file").get
      file.ref.moveTo(seqFile, replace = true)
    }

    val size = FileUtils.sizeOf(seqFile);
    val mSize = size / (1024 * 1024);
    if (mSize >= 1) {
      Utils.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> "false", "message" -> "Sequences is too long(over 1MB)!"))
    } else {
      val outFile = new File(tmpDir, "out.txt")
      val treeFile = new File(tmpDir, "tree.txt")
      val treeCommand = data.tree match {
        case "none" => ""
        case "tree1" => s" -tree1 ${treeFile.unixPath}"
        case "tree2" => s" -tree2 ${treeFile.unixPath}"
      }
      val command =
        s"""
           |muscle -in ${seqFile.unixPath} -verbose -log -fasta -out ${outFile.unixPath} -group ${treeCommand}
           |""".stripMargin
      val execCommand = CommandExec().exec { b =>
        CommandData(tmpDir, List(command))
      }
      if (execCommand.isSuccess) {
        val str = FileUtils.readFileToString(outFile)
        val treeStr = if (treeFile.exists()) {
          FileUtils.readFileToString(treeFile)
        } else ""
        Utils.deleteDirectory(tmpDir)
        Ok(Json.obj("out" -> str, "tree" -> treeStr, "valid" -> true))
      } else {
        Utils.deleteDirectory(tmpDir)
        Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
      }
    }
  }

  def downloadTree = Action { implicit request =>
    val data = FormTool.svgForm.bindFromRequest().get
    val dataFile = Files.createTempFile("tmp", ".svg").toFile
    val str = data.svgHtml.replaceAll("</svg>", "\n" + Tool.phylotreeCss + "</svg>")
    FileUtils.writeStringToFile(dataFile, str, "UTF-8")
    Ok.sendFile(dataFile, onClose = () => {
      Files.deleteIfExists(dataFile.toPath)
    }).withHeaders(
      CONTENT_DISPOSITION -> "attachment; filename=tree.svg",
      CONTENT_TYPE -> "application/x-download"
    )
  }

  def primerDesignBefore = Action { implicit request =>
    Ok(views.html.tools.primerDesign())
  }

  def primer = Action { implicit request =>
    val data = FormTool.primerForm.bindFromRequest.get
    val data2 = FormTool.primerForm2.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val seqFile = new File(tmpDir, "seq.fa")
    val outFile = new File(tmpDir, "out.txt")
    val execCommand = (data2.method match {
      case "Range" =>
        val faFile = Tool.getGenomeFile(data.species)
        val command =
          s"""
             |samtools faidx  ${faFile.unixPath} ${data.chr}:${data.start}-${data.end}  >${seqFile.unixPath}
             |""".stripMargin
        CommandExec().exec { b =>
          CommandData(tmpDir, List(command))
        }.map { b =>
          seqFile.lines.tail.mkString.toFile(seqFile)
        }
      case "Seq" =>
        data2.queryText.toFile(seqFile)
        CommandExec()
    }).exec { b =>
      val seqStr = seqFile.str
      val input =
        s"""
           |SEQUENCE_ID=example
           |SEQUENCE_TEMPLATE=${seqStr}
           |SEQUENCE_TARGET=${data.SEQUENCE_TARGET}
           |PRIMER_TASK=generic
           |PRIMER_PICK_LEFT_PRIMER=1
           |PRIMER_PICK_INTERNAL_OLIGO=1
           |PRIMER_PICK_RIGHT_PRIMER=1
           |PRIMER_OPT_SIZE=${data.PRIMER_OPT_SIZE}
           |PRIMER_MIN_SIZE=${data.PRIMER_MIN_SIZE}
           |PRIMER_MAX_SIZE=${data.PRIMER_MAX_SIZE}
           |PRIMER_OPT_TM=${data.PRIMER_OPT_TM}
           |PRIMER_MAX_TM=${data.PRIMER_MAX_TM}
           |PRIMER_MIN_TM=${data.PRIMER_MIN_TM}
           |PRIMER_OPT_GC_PERCENT=${data.PRIMER_OPT_GC_PERCENT}
           |PRIMER_MAX_GC=${data.PRIMER_MAX_GC}
           |PRIMER_MIN_GC=${data.PRIMER_MIN_GC}
           |PRIMER_MAX_NS_ACCEPTED=${data.PRIMER_MAX_NS_ACCEPTED}
           |PRIMER_MAX_POLY_X=${data.PRIMER_MAX_POLY_X}
           |PRIMER_INTERNAL_MAX_POLY_X=${data.PRIMER_INTERNAL_MAX_POLY_X}
           |PRIMER_MAX_SELF_ANY=${data.PRIMER_MAX_SELF_ANY}
           |PRIMER_MAX_SELF_END=${data.PRIMER_MAX_SELF_END}
           |PRIMER_PAIR_MAX_COMPL_ANY=${data.PRIMER_PAIR_MAX_COMPL_ANY}
           |PRIMER_PAIR_MAX_COMPL_END=${data.PRIMER_PAIR_MAX_COMPL_END}
           |PRIMER_PRODUCT_SIZE_RANGE=${data.PRIMER_PRODUCT_SIZE_RANGE.split(",").mkString(" ")}
           |P3_FILE_FLAG=1
           |SEQUENCE_INTERNAL_EXCLUDED_REGION=${data.SEQUENCE_TARGET}
           |PRIMER_EXPLAIN_FLAG=1
           |=
           |""".stripMargin.trim
      val settingFile = new File(tmpDir, "setting.txt")

      input.toFile(settingFile)
      val command =
        s"""
           |dos2unix setting.txt
           |primer3_core  -format_output -default_version=2 -io_version=4 -strict_tags -output=${outFile.unixPath} ${settingFile.unixPath}
           |""".stripMargin
      CommandData(tmpDir, command)
    }
    if (execCommand.isSuccess) {
      try {
        val out = outFile.str
        val start = data.start match {
          case "" => 1
          case x => x.toInt
        }
        val result = if (out.contains("NO PRIMERS FOUND")) {
          Tool.getNonePrimerResult(tmpDir, start)
        } else {
          Tool.getPrimerResult(tmpDir, start)
        }
        Tool.deleteDirectory(tmpDir)
        Ok(Json.obj("result" -> result, "valid" -> true))
      } catch {
        case e: Exception =>
          Tool.deleteDirectory(tmpDir)
          e.printStackTrace()
          Ok(Json.obj("valid" -> false, "message" -> e.getMessage))
      }

    } else {
      Tool.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> false, "message" -> execCommand.errorInfo))
    }
  }

  def mummerBefore = Action { implicit request =>
    Ok(views.html.tools.mummer())
  }

  def mummer = Action(parse.multipartFormData) { implicit request =>
    val data = FormTool.mummerForm.bindFromRequest.get
    val tmpDir = Tool.createTempDirectory("tmpDir")
    val targetFile = new File(tmpDir, "target.fa")
    data.targetText.map { targetText =>
      FileUtils.writeStringToFile(targetFile, targetText)
    }.getOrElse {
      val file = request.body.file("targetFile").get
      file.ref.moveTo(targetFile, replace = true)
    }
    val queryFile = new File(tmpDir, "query.fa")
    data.queryText.map { queryText =>
      FileUtils.writeStringToFile(queryFile, queryText)
    }.getOrElse {
      val file = request.body.file("queryFile").get
      file.ref.moveTo(queryFile, replace = true)
    }
    val command =
      s"""
         |perl ${Tool.svBin.unixPath}/SV_finder.pl ${targetFile.unixPath} ${queryFile.unixPath} --prefix data --tfix target --qfix query --nocomp --outdir output --locate
       """.stripMargin
    val execCommand = CommandExec().exec { b =>
      CommandData(tmpDir, List(command))
    }
    val json = if (execCommand.isSuccess) {
      val file1 = new File(tmpDir, "output/02.Lastz/Target-Query.parallel.png")
      val base641 = file1.base64
      val file2 = new File(tmpDir, "output/02.Lastz/Target-Query.xoy.png")
      val base642 = file2.base64
      val axtFile = new File(tmpDir, "output/02.Lastz/all.axt")
      val axtString = FileUtils.readFileToString(axtFile)
      Json.obj("base641" -> base641, "base642" -> base642, "axtString" -> axtString)
    } else {
      Json.obj("valid" -> "false", "message" -> execCommand.errorInfo)
    }
    Tool.deleteDirectory(tmpDir)
    Ok(json)
  }

  def geneWiseBefore = Action { implicit request =>
    Ok(views.html.tools.geneWise())
  }

  def geneWise = Action { implicit request =>
    val data = FormTool.geneWiseForm.bindFromRequest.get
    val tmpDir = Files.createTempDirectory("tmpDir").toFile
    val proteinFile = new File(tmpDir, "protein.fa")
    val buffer = data.method match {
      case "hand" => List(data.proteinSeq)
    }
    buffer.toFile(proteinFile)
    val dnaFile = new File(tmpDir, "dna.fa")
    FileUtils.writeStringToFile(dnaFile, data.dnaSeq, "UTF-8")
    val outFile = new File(tmpDir, "out.txt")
    val execCommand = CommandExec().exec { b =>
      val para = if (data.para) {
        "-para"
      } else ""
      val pretty = if (data.pretty) {
        "-pretty"
      } else ""
      val genes = if (data.genes) {
        "-genes"
      } else ""
      val trans = if (data.trans) {
        "-trans"
      } else ""
      val cdna = if (data.cdna) {
        "-cdna"
      } else ""
      val embl = if (data.embl) {
        "-embl"
      } else ""
      val ace = if (data.ace) {
        "-ace"
      } else ""
      val gff = if (data.gff) {
        "-gff"
      } else ""
      val diana = if (data.diana) {
        "-diana"
      } else ""
      val splice = data.splice match {
        case "flat" => ""
        case "model" => "-nosplice_gtag"
      }
      val command =
        s"""
           |genewise ${proteinFile.unixPath} ${dnaFile.unixPath}  -kbyte 4000 -init ${data.init} -null ${data.random} -alg ${data.alg} ${para} ${pretty} ${genes} ${trans} ${cdna} ${embl} ${ace} ${gff} ${diana} ${splice} >${outFile.unixPath}
           |""".stripMargin
      CommandData(tmpDir, List(command))
    }
    if (execCommand.isSuccess) {
      val str = FileUtils.readFileToString(outFile)
      Utils.deleteDirectory(tmpDir)
      Ok(Json.toJson(str))
    } else {
      Utils.deleteDirectory(tmpDir)
      Ok(Json.obj("valid" -> "false", "message" -> execCommand.errorInfo))
    }
  }


}
